package kvoog.arkanoid.physics;

import java.util.ArrayList;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.ChainShape;
import com.badlogic.gdx.physics.box2d.World;

/** 
 * Provides an easier way to declare Box2D Bodies.
 * 
 * Adapted from Gemserk classes BodyBuilder and FixtureDefBuilder (http://blog.gemserk.com/) 
 */
public class BodyBuilder {

	private World world;
	private BodyDef bodyDef;
	private Object userData;
	private FixtureDef fixtureDef;
	private Object fixtureUserData;
	private ArrayList<FixtureDef> fixtureDefs;
	private ArrayList<Object> fixtureUserDatas;

	public BodyBuilder(World world) {
		this.world = world;
		this.fixtureDefs = new ArrayList<FixtureDef>();
		this.fixtureUserDatas = new ArrayList<Object>();
		reset();
	}
	
	public void reset() {
		bodyDef = new BodyDef();
		userData = null;
		
		resetFixture();
		
		if (fixtureDefs != null) {
			for (int i = 0; i < fixtureDefs.size(); i++) {
				FixtureDef fixtureDef = fixtureDefs.get(i);
				fixtureDef.shape.dispose();
			}
		}
		
		fixtureDefs.clear();
		fixtureUserDatas.clear();
	}
	
	public BodyBuilder type(BodyType type) {
		bodyDef.type = type;
		return this;
	}
	
	public BodyBuilder position(float x, float y) {
		bodyDef.position.set(x, y);
		return this;
	}
	
	public BodyBuilder angle(float angle) {
		bodyDef.angle = angle;
		return this;
	}
	
	public BodyBuilder fixedRotation() {
		bodyDef.fixedRotation = true;
		return this;
	}
	
	public BodyBuilder linearVelocity(float x, float y) {
		bodyDef.linearVelocity.set(x, y);
		return this;
	}
	
	public BodyBuilder linearDumping(float linearDumping) {
		bodyDef.linearDamping = linearDumping;
		return this;
	}
	
	public BodyBuilder angularDumping(float angularDumping) {
		bodyDef.angularDamping = angularDumping;
		return this;
	}
	
	public BodyBuilder bullet() {
		bodyDef.bullet = true;
		return this;
	}
	
	public BodyBuilder userData(Object userData) {
		this.userData = userData;
		return this;
	}
	
	public BodyBuilder resetFixture() {
		fixtureDef = new FixtureDef();
		fixtureUserData = null;
		return this;
	}
	
	public BodyBuilder addFixture() {
		fixtureDefs.add(fixtureDef);
		fixtureUserDatas.add(fixtureUserData);
		return this;
	}
	
	public BodyBuilder sensor() {
		fixtureDef.isSensor = true;
		return this;
	}
	
	public BodyBuilder boxShape(float w, float h) {
		PolygonShape shape = new PolygonShape();
		shape.setAsBox(w, h);
		fixtureDef.shape = shape;
		return this;
	}
	
	public BodyBuilder circleShape(float radius) {
		CircleShape shape = new CircleShape();
		shape.setRadius(radius);
		fixtureDef.shape = shape;
		return this;
	}

	public BodyBuilder circleShape(Vector2 center, float radius) {
		CircleShape shape = new CircleShape();
		shape.setRadius(radius);
		shape.setPosition(center);
		fixtureDef.shape = shape;
		return this;
	}

	public BodyBuilder polygonShape(Vector2[] vertices) {
		PolygonShape shape = new PolygonShape();
		shape.set(vertices);
		fixtureDef.shape = shape;
		return this;
	}
	
	public BodyBuilder chainShape(Vector2[] vertices, boolean loop) {
		ChainShape shape = new ChainShape();
		if (loop)
			shape.createLoop(vertices);
		else
			shape.createChain(vertices);
		fixtureDef.shape = shape;
		return this;
	}
	
	public BodyBuilder density(float density) {
		fixtureDef.density = density;
		return this;
	}
	
	public BodyBuilder friction(float friction) {
		fixtureDef.friction = friction;
		return this;
	}
	
	public BodyBuilder restitution(float restitution) {
		fixtureDef.restitution = restitution;
		return this;
	}
	
	public BodyBuilder categoryBits(short categoryBits) {
		fixtureDef.filter.categoryBits = categoryBits;
		return this;
	}
	
	public BodyBuilder maskBits(short maskBits) {
		fixtureDef.filter.maskBits = maskBits;
		return this;
	}
	
	public BodyBuilder fixtureUserData(Object fixtureUserData) {
		this.fixtureUserData = fixtureUserData;
		return this;
	}

	public Body build() {
		Body body = world.createBody(bodyDef);
		body.setUserData(userData);
		
		// TODO add mass data
		
		for (int i = 0; i < fixtureDefs.size(); i++) {
			FixtureDef fixtureDef = fixtureDefs.get(i);
			Fixture fixture = body.createFixture(fixtureDef);
			fixture.setUserData(fixtureUserDatas.get(i));
		}
		
		reset();
		
		return body;
	}
}
